#include "helper.h"
#include "monitor.h"
#include "reg.h"
#include <stdio.h>

extern uint32_t instr;
extern char assembly[80];

const int MASK_14 = 0xfffC0000;
const int MASK_16 = 0xffff0000;
const int MASK_24 = 0xffffff00;


/* decode I-type instrucion with unsigned immediate */
static void decode_imm_type(uint32_t instr) {

	op_src1->type = OP_TYPE_REG; 
	//get opcode
	op_src1->reg = (instr & RS_MASK) >> (RT_SIZE + IMM_SIZE);
	//get 
	
	op_src1->val = reg_w(op_src1->reg);

	//RS

	
	op_src2->type = OP_TYPE_IMM;
	//get immediate num

	
	op_src2->imm = instr & IMM_MASK;
	
	op_src2->val = op_src2->imm;
	//imm
	
	op_dest->type = OP_TYPE_REG;
	
	op_dest->reg = (instr & RT_MASK) >> (IMM_SIZE);
	//rt
	op_dest->val = reg_w(op_dest->reg);

}
//get information of one demmand

int is_neg_pos_16(int num){
	return num & 0x8000;
}
//判断16位数字最高位是什么

int is_neg_pos_8(int num){
	return num & 0x80;
}
//判断8位数字最高位是什么

uint32_t sign_extend(uint16_t modifying_num){
	uint32_t final_num;
	
	if(is_neg_pos_16(modifying_num)){
		final_num = modifying_num | MASK_16;
	}//负数
	else{
		final_num = modifying_num | 0x00000000;
	}

	return final_num;

}

uint32_t sign_extend_8(uint8_t modifying_num){
	uint32_t final_num;
	
	if(is_neg_pos_8(modifying_num)){
		final_num = modifying_num | MASK_24;
	}//负数
	else{
		final_num = modifying_num | 0x00000000;
	}

	return final_num;

}

uint32_t sign_extend_24(uint8_t modifying_num){
	uint32_t final_num;
	
	if(is_neg_pos_8(modifying_num)){
		final_num = modifying_num | MASK_24;
	}//负数
	else{
		final_num = modifying_num | 0x00000000;
	}

	return final_num;

}


make_helper(lui) {

	decode_imm_type(instr);
	reg_w(op_dest->reg) = (op_src2->val << 16);
	sprintf(assembly, "lui   %s,   0x%04x", REG_NAME(op_dest->reg), op_src2->imm);
}

make_helper(ori) {

	decode_imm_type(instr);
	reg_w(op_dest->reg) = op_src1->val | op_src2->val;
	sprintf(assembly, "ori   %s,   %s,   0x%04x", REG_NAME(op_dest->reg), REG_NAME(op_src1->reg), op_src2->imm);
}

make_helper(andi){
	decode_imm_type(instr);
	reg_w(op_dest->reg) = op_src1->val & op_src2->val;
	sprintf(assembly, "andi   %s,   %s,   0x%04x", REG_NAME(op_dest->reg), REG_NAME(op_src1->reg), op_src2->imm);
}

make_helper(addi){
	decode_imm_type(instr);
	int tmp_val = sign_extend(op_src2->val);

	uint32_t tmp = op_src1->val + tmp_val;
	if ((int)(tmp ^ tmp_val) < 0 && (int) (tmp ^ op_src1->val) < 0){
		exception(Ov , 0);
	}
	else{
		reg_w(op_dest->reg) = tmp;
	}
	sprintf(assembly, "addi   %s,   %s,   0x%04x", REG_NAME(op_dest->reg), REG_NAME(op_src1->reg), op_src2->imm);
}

make_helper(addiu){
	decode_imm_type(instr);

	reg_w(op_dest->reg) = op_src1->val + sign_extend(op_src2->val);

	sprintf(assembly, "addiu   %s,   %s,   0x%04x", REG_NAME(op_dest->reg), REG_NAME(op_src1->reg), op_src2->imm);
}

make_helper(bgtz){
	
	decode_imm_type(instr);

	int target_val = sign_extend(op_src2->val) << 2;

	if((int)op_src1->val > 0){
		
		cpu.pc += target_val;

	}
	sprintf(assembly, "bgtz  %s , 0x%04x ", REG_NAME(op_src1->reg), op_src2->imm);


}

make_helper(bgez){
	decode_imm_type(instr);

	int offset = sign_extend(op_src2->val);
	int target = offset << 2;
	
	if(op_dest->val == 1)
	{
		if((int)(op_src1->val) >= 0)
		{	
			cpu.pc += target;
		}
		sprintf(assembly, "bgez   %s,   0x%04x", REG_NAME(op_src1->reg), op_src2->imm);
	}
	else if(op_dest->val == 0)//bltz
	{
		if((int)(op_src1->val) < 0)
		{	
			cpu.pc += target;
		}
		sprintf(assembly, "bltz   %s,   0x%04x", REG_NAME(op_src1->reg), op_src2->imm);
	}
	else if(op_dest->val == 0x10001)//bgezal
	{
		cpu.gpr[31]._32 = cpu.pc + 4;
		if((int)(op_src1->val) >= 0)
		{
			cpu.pc += target;
		}
		sprintf(assembly, "bgezal   %s,   0x%04x", REG_NAME(op_src1->reg), op_src2->imm);
	}
	else if(op_dest->val == 0x10000)//bltzal
	{
		cpu.gpr[31]._32 = cpu.pc + 4;
		if((int)(op_src1->val) < 0)
		{
			cpu.pc += target;
		}
		sprintf(assembly, "btlzal   %s,   0x%04x", REG_NAME(op_src1->reg), op_src2->imm);
	}

}

make_helper(sb){
	decode_imm_type(instr);

	int offset = sign_extend(op_src2->val);

	int vAddr = op_src1->val + offset;

	if(vAddr > 0x9fffffff){
		vAddr = vAddr & 0x1fffffff;
	}else{
		vAddr = vAddr & 0x7fffffff;
	}


	int data = op_dest->val & 0xff;
	
	mem_write(vAddr , 1 , data );

	sprintf(assembly, "sb   %s,   0x%04x", REG_NAME(op_dest->reg), op_src2->imm);

}

make_helper(sh){
	decode_imm_type(instr);

	int offset = sign_extend(op_src2->val);

	int vAddr = op_src1->val + offset;

	if( (vAddr & 0x1 ) != 0 ){
		exception(AdES , vAddr);
		//address error
	}else{
		vAddr = vAddr & 0x1fffffff;

		int data = op_dest->val & 0xffff;
		//2bytes
	
		mem_write(vAddr , 2 , data );	
	}
	

	sprintf(assembly, "sh   %s,   0x%04x", REG_NAME(op_dest->reg), op_src2->imm);

}

make_helper(lb){
	decode_imm_type(instr);
	uint32_t addr = op_src1->val  + sign_extend(op_src2->val);
	if(addr > 0x9fffffff){
		addr = addr & 0x1fffffff;
	}else{
		addr = addr & 0x7fffffff;
	}
	//map address

	int ans = mem_read(addr, 1);
	//extend
	
	reg_w(op_dest->reg) = sign_extend_8(ans);
	sprintf(assembly, "lb    %s,   0x%04x(%s)", REG_NAME(op_dest->reg), op_src2->val, REG_NAME(op_src1->reg));

}

make_helper(lbu){
	decode_imm_type(instr);
	uint32_t vAddr = op_src1->val  + sign_extend(op_src2->val);
	if(vAddr  > 0x9fffffff)	{
		vAddr  = vAddr  & 0x1fffffff;
	}else{
		vAddr  = vAddr  & 0x7fffffff;
	}
	int ans = mem_read(vAddr , 1);
	//no extend

	reg_w(op_dest->reg) = ans;
	sprintf(assembly, "lb    %s,   0x%04x(%s)", REG_NAME(op_dest->reg), op_src2->val, REG_NAME(op_src1->reg));

}

make_helper(lw){
	decode_imm_type(instr);

	int offset = sign_extend(op_src2->val);

	int vAddr = op_src1->val + offset;

	if((vAddr & 0x3) != 0){
		exception(AdEL , vAddr);
	}else{
		vAddr = vAddr & 0x1fffffff;

		int data = mem_read(vAddr , 4);

		reg_w(op_dest->reg) = data;
	}

	sprintf(assembly, "lw   %s,   0x%04x", REG_NAME(op_dest->reg), op_src2->imm);

}

make_helper(lh){
	decode_imm_type(instr);

	int offset = sign_extend(op_src2->val);

	int vAddr = op_src1->val + offset;

	if ((vAddr & 0x1) != 0){
		exception(AdEL , vAddr);

	}else{
		vAddr = vAddr & 0x1fffffff;

		int data = mem_read(vAddr , 2);

		reg_w(op_dest->reg) = sign_extend(data);
	}


	sprintf(assembly, "lh   %s,   0x%04x", REG_NAME(op_dest->reg), op_src2->imm);

}

make_helper(lhu){
	decode_imm_type(instr);

	int offset = sign_extend(op_src2->val);

	int vAddr = op_src1->val + offset;

	if ((vAddr & 0x1) != 0){
		exception(AdEL , vAddr);

	}else{
		vAddr = vAddr & 0x1fffffff;

		int data = mem_read(vAddr , 2);

		reg_w(op_dest->reg) = data;
	}


	sprintf(assembly, "lhu   %s,   0x%04x", REG_NAME(op_dest->reg), op_src2->imm);

}


make_helper(sw){
	decode_imm_type(instr);
	
	uint32_t offset =sign_extend(op_src2->val);

	uint32_t vAddr = op_src1->val + offset;


	if((vAddr & 0x3) != 0){
		exception(AdES , vAddr);
	}else{
		vAddr = vAddr & 0x1fffffff;

		mem_write(	vAddr , 4 , op_dest->val );
	}

	sprintf(assembly, "sw   %s,   0x%04x", REG_NAME(op_dest->reg), op_src2->imm);

}

make_helper(bne){
	decode_imm_type(instr);
	// printf("flag ----------- %04x , %04x\n" , op_src1->val , op_dest->val);
	
	// printf("flag ----------- %04x , %04x\n" , reg_w(op_src1->reg) , reg_w(op_dest->reg));

	if(op_src1->val != op_dest->val)
	{
		int offset = sign_extend(op_src2->val);
		cpu.pc += (offset << 2);
	}
	sprintf(assembly, "bne   %s,   %s,   0x%04x", REG_NAME(op_dest->reg), REG_NAME(op_src1->reg), op_src2->imm);

}


make_helper(beq){
	decode_imm_type(instr);

	if ((op_src1->val) == (op_dest->val) ){
		int target_offset;

		int temp_val = sign_extend(op_src2->val);

		target_offset = temp_val << 2;

		cpu.pc += target_offset;
	}

	sprintf(assembly, "beq   %s,   %s,   0x%04x", REG_NAME(op_dest->reg), REG_NAME(op_src1->reg), op_src2->imm);

}

make_helper(slti){

	decode_imm_type(instr);

	int target_imm = op_src2->imm;
	if(is_neg_pos_16(op_src2->imm)){
		target_imm = target_imm | MASK_16;
	}
	
	if(op_src1->val < target_imm){
		op_dest->val = 1;
	}
	else{
		op_dest->val = 0;
	}
	sprintf(assembly, "slti   %s,   %s,   0x%04x", REG_NAME(op_dest->reg), REG_NAME(op_src1->reg), op_src2->imm);

}


make_helper(blez){
	decode_imm_type(instr);
	
	uint32_t target =sign_extend(op_src2->val) << 2;

	if(op_src1->val <= 0){
		cpu.pc += target;
	}
	sprintf(assembly, "blez   %s,      0x%04x", REG_NAME(op_src1->reg), op_src2->imm);
	
}


make_helper(sltiu){
	decode_imm_type(instr);

	uint32_t final_value = sign_extend(op_src2->val);
	//extend imm

	if ( (int)op_src1->val  <  (int) final_value ){
		reg_w(op_dest->reg) = 1;
	}else{
		reg_w(op_dest->reg) = 0;
	}

	sprintf(assembly, "sltiu   %s,   %s,   0x%04x", REG_NAME(op_dest->reg), REG_NAME(op_src1->reg), op_src2->imm);

}

make_helper(xori){
	decode_imm_type(instr);

	uint32_t final_val = sign_extend(op_src2->val);

	reg_w(op_dest->reg) = op_src1->val ^ final_val;

	sprintf(assembly, "xori   %s,   %s,   0x%04x", REG_NAME(op_dest->reg), REG_NAME(op_src1->reg), op_src2->imm);
}

make_helper(mthi){
	decode_imm_type(instr);
	cpu.hi = op_src1->val;
	sprintf(assembly, "mthi   %s", REG_NAME(op_dest->reg));
}

make_helper(mtlo){
	decode_imm_type(instr);
	cpu.lo = op_src1->val;
	sprintf(assembly, "mtlo   %s", REG_NAME(op_dest->reg));
}